import java.util.LinkedList;
import java.util.Random;
import java.io.*;

/**
 * A class for representing the Tetris Board.
 * (0,0) represents the lower left corner and (m-1,n-1) the upper right corner
 * @author (Oscar Ohlsson) (Marcus Dicander)
 */
public class Board implements Serializable
{
    /**
    * A matrix containing the bricks in this Board, the format is
	* Brick[columns][rows].
    */
    private Brick[][] board;

    /**
    * The number of rows in the board
    */
    private int rows;

    /**
    * The number of columns in the board
    */
    private int columns;

    /**
    * A generator for random numbers. Used for adding powerups
    */
    private Random rand;

    /**
    * The number of non-powerup bricks in the board. Used for adding powerups
    */
    private int counter;

	/**
	* Used to speed up hole counting
	*/
	//private int[][] holeCountMatrix;
	//private int holeCountIterator = 0;
	//private int holeCount = 0;


    /**
     * Constructor for objects of class Board
     *
     * @param width   The width of the board
     * @param height  The height of the board
     */
    public Board(int columns, int rows, int seed)
    {
        rand = new Random(seed);
        counter = 0;
        this.rows = rows;
        this.columns = columns;
        board = new Brick[columns][rows];
		//holeCountMatrix = new int[columns][rows];
    }

	/**
	 * Returns the number of rows that this board contains.
	 *
	 * @return the number of rows
	 */
	public int getRows() {
		return rows;
	}

	/**
	 * Returns the number of columns that this board contains.
	 *
	 * @return the number of columns
	 */
	public int getColumns() {
		return columns;
	}

    /**
     * Removes all the Bricks in the specified row.
     * All the bricks above the specified row that has been fixated will move down one step.
     * If any of the removed bricks contains a powerup it will be extracted and returned.
     * This method is called by doActions in the GameLogic class.
     *
     * @param  target  the index of the row to be removed
     * @return a list containing the extracted powerups
     */
    public LinkedList<Integer> removeRow(int targetRow)
    {
        LinkedList<Integer> powerups = new LinkedList<Integer>();
        Brick b;
		int removedBricks = 0;
        for (int x = 0; x < this.columns; x++) { // collects all powerups from target row
            b = board[x][targetRow];
            if (b != null && b.getPowerup() != 0) {
                powerups.add(b.getPowerup());
            }
			if (b != null) {
				removedBricks++;
			}
        }
        for (int y = targetRow; (y+1) < this.rows; y++) { // copies above row from target and up
            for (int x = 0; x < this.columns; x++) {
                board[x][y] = board[x][y+1];
            }
            for (int x = 0; x < this.columns; x++) { // clears the copied row (just in case)
                board[x][y+1] = null;
            }
        }
        for(int x=0; x < this.columns ; x++){ // removes top row, just in case
        	board[x][this.rows-1] = null;
        }
        counter -= (removedBricks - powerups.size());
        return powerups;
    }

	public void mergeBricks() {
		for (int x=0; x<columns; x++) {
			for (int y=0; y<rows; y++) {
				if (board[x][y] == null) continue;
				Player p = board[x][y].getPlayer();

				int edges = 0;
				if (y == 0 || board[x][y-1] == null || board[x][y-1].getPlayer() != p || board[x][y-1].getPowerup() != 0) edges |= 8;
				if (y == rows-1 || board[x][y+1] == null || board[x][y+1].getPlayer() != p || board[x][y+1].getPowerup() != 0) edges |= 2;
				if (x == 0 || board[x-1][y] == null || board[x-1][y].getPlayer() != p || board[x-1][y].getPowerup() != 0) edges |= 1;
				if (x == columns-1 || board[x+1][y] == null || board[x+1][y].getPlayer() != p || board[x+1][y].getPowerup() != 0) edges |= 4;
				board[x][y].setEdges(edges);
			}
		}
		//searchHoles();
	}

	//public int getHoleCount() {
	//	return holeCount;
	//}

	/*public void searchHoles() {
		int x=0, y=rows-1;
		// switch between 0 and 1, so we dont have to clear the matrix
		holeCountIterator = (holeCountIterator == 0 ? 1 : 0);

		// breadth-first search to find non-holes
		// begin from all top positions in the board
		for (x=0; x<columns; x++) {
			if (board[x][y] == null) {
				LinkedList<Tuple2<Integer,Integer>> search = new LinkedList<Tuple2<Integer,Integer>>();
				search.add(new Tuple2<Integer,Integer>(x, y));
				while (search.size() > 0) {
					Tuple2<Integer,Integer> p = search.removeFirst();
					if (p.second < rows && p.second >= 0 &&
						p.first < columns && p.first >= 0 &&
						holeCountMatrix[p.first][p.second] != holeCountIterator &&
						board[p.first][p.second] == null) {

						holeCountMatrix[p.first][p.second] = holeCountIterator;

						search.addLast(new Tuple2<Integer,Integer>(p.first+1, p.second));
						search.addLast(new Tuple2<Integer,Integer>(p.first-1, p.second));
						search.addLast(new Tuple2<Integer,Integer>(p.first, p.second+1));
						search.addLast(new Tuple2<Integer,Integer>(p.first, p.second-1));
					}
				}
			}
		}

		// then count how many positions in the board that wasnt reached by the breadth-first search
		int numberOfHoles = 0;
		for (x=0; x<columns; x++) {
			for (y=0; y<rows; y++) {
				if (holeCountMatrix[x][y] != holeCountIterator && board[x][y] == null) {
					numberOfHoles++;
				}
				holeCountMatrix[x][y] = holeCountIterator;
			}
		}

		holeCount = numberOfHoles;
	}*/

    /**
     * Fixates a Piece on the Board by splitting it into Bricks and moving them to this Board.
     * This method is called by doActions in the GameLogic class.
     *
     * @param  x       the x coordinate of top left corner of the piece
     * @param  y       the y coordinate of top left corner of the piece
     * @param  piece   the piece to be fixated
     */
    public void fixPiece(int x, int y, Piece piece)
    {
        Brick[][] matrix = piece.getBricks();
        for (int i = 0; i < matrix.length ; i++) {
            for (int j = 0; j < matrix[i].length ; j++) {
                if (matrix[i][j] != null) {
					// if empty slot or filled with a powerup, increase the nonpowerup counter
					if (board[x+i][y+j] == null || board[x+i][y+j].getPowerup()!=0) {
						counter ++;
					}
                    board[x+i][y+j]=matrix[i][j];
                }
            }
        }
    }

    /**
     * Removes the Brick at the specified position.
     * This method is called by doActions in the GameLogic class.
     *
     * @param  x  The x-coordinate of the brick to be removed
     * @param  y  The y-coordinate of the brick to be removed
     */
    public Brick removeBrick(int x, int y)
    {
        Brick b = board[x][y];
        board[x][y] = null;
        if (b != null && b.getPowerup() == 0) {
			counter--;
		}
        return b;
    }

    /**
     * Fixates a Piece on the Board by splitting it into Bricks and moving them to this Board.
     * This method is called by doActions in the GameLogic class.
     *
     * @param  x  The x-coordinate of the brick
     * @param  y  The y-coordinate of the brick
     * @return The brick at the specified position, or null of the position was empty
     */
    public Brick getBrick(int x, int y)
    {
        return board[x][y];
    }

    public void setBrick(int x, int y, Brick b)
    {
		if (board[x][y] != null && board[x][y].getPowerup() == 0) {
			counter --;
		}
		if (b != null && b.getPowerup() == 0) {
			counter ++;
		}
        board[x][y] = b;
    }

    public Brick[] getRow(int y) {
       	Brick[] row = new Brick[columns];
       	for (int x=0; x<columns; x++) {
       		row[x] = board[x][y];
       	}
		return row;
    }

    /**
     * Replaces a random brick in the board with a powerup
     * Called each time a row is removed
     */
    public void addPowerup(int numPlayers) {
        if (counter > 0) {
			int[] types = {Multitris.POWERUP_G, Multitris.POWERUP_G,
							Multitris.POWERUP_N,
							Multitris.POWERUP_P, Multitris.POWERUP_P, Multitris.POWERUP_P,
							Multitris.POWERUP_C, Multitris.POWERUP_C, Multitris.POWERUP_C,
							Multitris.POWERUP_C, Multitris.POWERUP_C, Multitris.POWERUP_C,
							Multitris.POWERUP_S, Multitris.POWERUP_S, Multitris.POWERUP_S};
			int type = types[rand.nextInt(types.length)];//rand.nextInt(GameLogic.nrOfPowerups) + 1;
			if (numPlayers == 1 && type == Multitris.POWERUP_N) {
				type = Multitris.POWERUP_C;
			}
			int x =  rand.nextInt(counter) + 1;
			int n = 1;
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < columns; j++) {
					if (board[j][i] != null && board[j][i].getPowerup() == 0) {
						if (n == x) {
							board[j][i].setPowerup(type);
							counter--;
							return;
						} else {
							n++;
						}
					}
				}
			}
		}
    }

}
